import json
import logging

import numpy as np

from api.serial.analyse import cal_madis_list, get_every_seg_for_positive_portrait_v2, cal_segmentation_madis, \
    cal_segmentation_time
from api.serial.sample import NpEncoder, ProductSample
from detecter import settings
from detecter.settings import Collection_frequency

logger = logging.getLogger('serial.sample')


class FigureResult:
    '''
    json_str是从数据库中恢复画像的参数
    根据从数据库中恢复的参数，计算得到了构成画像的一些统计信息和数据，例如平均值、方差等。这些信息可以用于后续的图像生成或其他处理。
    '''

    def __init__(self, json_str):
        self.is_result_safe = False
        if json_str:
            # 正样本画像的实际压力，螺杆位置，速度以及方差
            result = json.loads(json_str)
            # 正样本画像的实际压力，螺杆位置，速度以及方差
            self.actual_pressure = result['actual_pressure']
            self.actual_location = result['actual_location']
            self.actual_speed = result['actual_speed']
            self.pressure_var = result['pressure_var']
            self.location_var = result['location_var']
            self.speed_var = result['speed_var']
            self.feature = result['feature']
            self.feature_var = result['feature_var']
            self.setting_pressure = result['setting_pressure']
            self.seg_points = result['seg_points']
            self.each_seg = result['each_seg']
            self.dist_total_mean = result['dist_total_mean']
            self.dist_total_var = result['dist_total_var']
            self.dist_means = result['dist_means']
            self.dist_vars = result['dist_vars']
            self.td_means = result['td_means']
            self.td_vars = result['td_vars']
            self.sample_count = result['sample_count']
            self.length = result['length']
            self.check_data_dump_safe()
        else:
            # 正样本画像的实际压力，螺杆位置，速度以及方差
            self.actual_pressure = ''
            self.actual_location = ''
            self.actual_speed = ''
            self.pressure_var = ''
            self.location_var = ''
            self.speed_var = ''
            self.feature = ''
            self.feature_var = ''
            self.setting_pressure = ''
            self.seg_points = ''
            self.each_seg = ''
            self.dist_total_mean = ''
            self.dist_total_var = ''
            self.dist_means = ''
            self.dist_vars = ''
            self.td_means = ''
            self.td_vars = ''
            self.sample_count = ''
            self.length = ''

    def build_portrait(self, good_file, all=False, length=None):
        # 从文件中获取最近1000个样本，不足1000个全部获取
        N, list_len, all_feat, actual_pressure_list, actual_location_list, actual_speed_list, setting_pressure_list, seg_points_list, id_list = \
            self.load_data_from_good_file(good_file, all=all, length=length)  # 每个列表里面存储的个数为 良品文件里面数据的条数或1000条
        portrait_actual_pressure = np.mean([i[:N] for i in actual_pressure_list if len(i) >= N], axis=0)  # 正样本画像的实际压力, 图表需要，得到的是一个均值列表
        portrait_actual_location = np.mean([i[:N] for i in actual_location_list if len(i) >= N], axis=0)  # 正样本画像的螺杆位置
        portrait_actual_speed = np.mean([i[:N] for i in actual_speed_list if len(i) >= N], axis=0)  # 正样本画像的速度, 图表需要
        portrait_setting_pressure = np.mean([i[:N] for i in setting_pressure_list if len(i) >= N], axis=0)  # 平均下来的设定压力会导致整体压力趋于平滑，最终导致计算结果的分段数变多
        # portrait_setting_pressure = np.array(setting_pressure_list[-1])  # 不妨使用最后一帧的设定压力作为评判标准

        # good_sample中的样本数
        self.set_sample_count(list_len)
        self.set_portrait_mean(N, portrait_actual_pressure.tolist(), portrait_actual_location.tolist(),
                               portrait_actual_speed.tolist())  # 将这几个算出来的值保存到figure实例属性
        self.set_portrait_pressure_setting(portrait_setting_pressure.tolist())
        # 方差 按列求方差
        pressure_var = np.var([i[:N] for i in actual_pressure_list if len(i) >= N], axis=0)
        location_var = np.var([i[:N] for i in actual_location_list if len(i) >= N], axis=0)
        speed_var = np.var([i[:N] for i in actual_speed_list if len(i) >= N], axis=0)
        self.set_portrait_var(pressure_var.tolist(), location_var.tolist(), speed_var.tolist())
        # 降采样
        sample_index = np.array(list(range(0, N, Collection_frequency)))
        portrait_feat = np.concatenate(
            [portrait_actual_pressure[sample_index], portrait_actual_location[sample_index],
             portrait_actual_speed[sample_index]])  # 均值拼接，代码有些冗余
        feat_var = np.concatenate([pressure_var[sample_index], location_var[sample_index], speed_var[sample_index]])  # 方差拼接

        self.set_portrait_total_feat(portrait_feat.tolist(), feat_var.tolist())
        # 高于N条记录的正样本整段的马氏距离列表
        dist_total_mean, dist_total_var = cal_madis_list(all_feat, portrait_feat, feat_var)
        self.set_dist_total_info(dist_total_mean.tolist(), dist_total_var.tolist())  # 这里没问题吗？
        # 根据实际压力和设定压力进行分段
        try:
            # portrait_seg_points = ProductSample.curve_segmentation(portrait_actual_pressure, portrait_setting_pressure, isSample=False)  # 获取一个分段点，但是这个分段点感觉有问题
            portrait_seg_points = self.get_portrait_seg(seg_points_list)   # 获取一个分段点，但是这个分段点感觉有问题
        except Exception as e:
            logger.error(f'正样本画像分段失败,{e}')
            raise e
        # 将保压分段点保存起来
        self.set_seg_points(portrait_seg_points)

        sample_index_seg_list, len_seg_list, feat_seg_list, feat_var_seg_list = [], [], [], []
        for i in range(len(portrait_seg_points) - 1):
            sample_index_seg, len_seg, portrait_feat_seg, feat_var_seg = \
                get_every_seg_for_positive_portrait_v2(N, sample_index, portrait_seg_points[i],
                                                       portrait_seg_points[i + 1],
                                                       portrait_actual_pressure,
                                                       portrait_actual_location, portrait_actual_speed, pressure_var,
                                                       location_var,
                                                       speed_var)
            sample_index_seg_list.append(sample_index_seg.tolist())
            len_seg_list.append(len_seg)
            feat_seg_list.append(portrait_feat_seg.tolist())
            feat_var_seg_list.append(feat_var_seg.tolist())
        # 存进采样分段后的正样本画像
        self.set_every_segs_info([sample_index_seg_list, len_seg_list, feat_seg_list, feat_var_seg_list])
        # get the gaussian distribution for every seg madis and time difference
        dist_segs_list, td_segs_list = [], []
        # 对每一个样本都进行遍历，对每一个样本计算射胶段和保压段的madis
        for i in range(len(id_list)):
            # 如果某一个样本的设定压力的样本点少于N个,就不作为正样本
            if len(setting_pressure_list[i]) < N:
                logger.error(f'编号为 {id_list[i]} 的样本 注射保压段总记录数小于 {N}, 不参与计算马氏距离和时间差的高斯分布')
                continue
            dist_segs = cal_segmentation_madis(actual_pressure_list[i], actual_location_list[i], actual_speed_list[i],
                                               seg_points_list[i], len_seg_list,
                                               feat_seg_list, feat_var_seg_list)  # 返回的dist_segs里面应该是有两个值的，一个对应射胶段的madis，一个对应保压段madis
            dist_segs_list.append(dist_segs)
            # 计算该样本在每一段与正样本画像的时间差
            td_list = cal_segmentation_time(seg_points_list[i], portrait_seg_points)  # 得到的列表里面包含两个值
            td_segs_list.append(td_list)
        dist_seg_mean_list, dist_seg_var_list, td_seg_mean_list, td_seg_var_list = [], [], [], []
        # 将多维内置列表转换成多维numpy数组，否则无法按列索引,而且不报错...
        dist_segs_list_np_arr = np.array(dist_segs_list)
        td_segs_list = np.array(td_segs_list)
        # 正样本在每段的马氏距离的平均值, 遍历列数,列数即分段的段数
        for i in range(np.size(dist_segs_list_np_arr, 1)):  # np.size(arr, 1) 返回数组的列数,这里的返回值就是2
            dist_seg_mean = np.mean(dist_segs_list_np_arr[:, i])
            dist_seg_mean_list.append(dist_seg_mean.tolist())
            # 正样本在每段的方差
            dist_seg_var = np.var(dist_segs_list_np_arr[:, i])
            dist_seg_var_list.append(dist_seg_var.tolist())
            # 正样本在每段的时间差距的均值
            td_seg_mean = np.mean(td_segs_list[:, i])
            td_seg_mean_list.append(td_seg_mean.tolist())
            # 正样本在每段的时间差距的方差
            td_seg_var = np.var(td_segs_list[:, i])
            td_seg_var_list.append(td_seg_var.tolist())
        self.set_dist_means(dist_seg_mean_list)
        self.set_dist_vars(dist_seg_var_list)
        self.set_td_means(td_seg_mean_list)
        self.set_td_vars(td_seg_var_list)
        logger.info("Flag")
        return self

    # 发现time_info[5]的数据有时候不能与实际的压力/位置/速度帧数量匹配，
    # 出现了time_info[5] = 705，但是实际压力/位置/速度数量为705，704，704
    # zxy 修改于20191115
    def load_data_from_good_file(self, file_name, portrait_size=1000, all=False, length=None):
        """
        从文件中获取样本信息
        :param file_name:  文件路径
        :param portrait_size: 用来构建正样本画像的样本数量
        :param all: 文件中是否全部用来构建画像
        :param length: 画像的长度,如果为None,则取这批样本中的最小值，如果不为None且小于最小值，则长度取length
        :return: data
        """
        # [self.id, self.time_info, self.seg_points, self.actual_pressure, self.actual_location, self.actual_speed]
        # [start_time, end_time, list_len, period_begin, period_end, period_len, cycle]
        actual_pressure_list = []
        actual_location_list = []
        actual_speed_list = []
        setting_pressure_list = []
        period_len_list = []
        seg_points_list = []
        all_feat = []
        id_list = []

        with open(file_name, mode='r') as reader:
            json_list = reader.readlines()
        list_len = len(json_list)
        if list_len == 0:
            raise Exception(f"{file_name}文件为空")

        if not all:
            begin_index = 0
            if list_len > portrait_size:  # 如果是大于1000的，取最后一千个。
                begin_index = list_len - portrait_size
            json_list = json_list[begin_index:]
            if begin_index > 0:  # 只保留最近1000条数据,将最后一千条数据重新写入csv文件中
                with open(file_name, mode='w') as writer:
                    writer.writelines(json_list)
        for json_str in json_list:
            # json_obj = json.loads(json_str)
            try:
                json_obj = json.loads(json_str)
            except:
                continue
            # zxy 修改于 20200111
            # 每条样本数据都得出一个最小长度并添加到period_len_list里面
            period_len_list.append(min(len(json_obj['actual_pressure']),
                                       len(json_obj['actual_location']),
                                       len(json_obj['actual_speed'])))  # 这三个列表的长度不是应该一样的吗？
        N = np.min(period_len_list)  # 得到所有样本中，所有数据列表的长度最小的

        if length:
            if length < N:
                N = length
            # 特征向量降采样 1/8
        sample_index = np.array(list(range(0, N, Collection_frequency)))
        for json_str in json_list:
            # json_obj = json.loads(json_str)
            try:
                json_obj = json.loads(json_str)
            except:
                continue
            ch1_arr = np.array(json_obj['actual_pressure'])
            ch2_arr = np.array(json_obj['actual_location'])
            speed_arr = np.array(json_obj['actual_speed'])
            # 如果出现了timeinfo[5]与后续实际压力或实际速度等特征数量不匹配的情况则该样本不用于构建正画像
            try:
                feat = np.concatenate([ch1_arr[sample_index], ch2_arr[sample_index], speed_arr[sample_index]])  # sample_index 保证了各部分的值的长度都是N，最后feat的总长度为3N，将实际压力实际位置实际速度先各自切片，再将三个切片拼接起来
                all_feat.append(feat)
                id_list.append(json_obj['id'])
                actual_pressure_list.append(json_obj['actual_pressure'])
                actual_location_list.append(json_obj['actual_location'])
                actual_speed_list.append(json_obj['actual_speed'])
                setting_pressure_list.append(json_obj['setting_pressure'])
                seg_points_list.append(json_obj['seg_points'])
            except IndexError:
                logger.warning("样本id为" + str(json_obj['id']) + "的数据部分帧丢失, 不用于建模")
                # 最后返回各种值，例如：actual_pressure_list里面存储的是len(json_list)个或者1000个子列表，每个子列表是一模产品相关的数据，list_len是good_file里面的数据的数量
        return N, list_len, all_feat, actual_pressure_list, actual_location_list, actual_speed_list, setting_pressure_list, seg_points_list, id_list

    def get_sample_count(self):
        return self.sample_count

    def set_sample_count(self, count):
        self.sample_count = count

    # 正样本画像的实际压力,正样本画像的螺杆位置,正样本画像的速度
    def set_portrait_mean(self, N, ch1, ch2, speed):
        self.length = N
        self.actual_pressure = ch1
        self.actual_location = ch2
        self.actual_speed = speed

    def get_portrait_mean(self):
        return [self.actual_pressure, self.actual_location, self.actual_speed]

    # 样本画像的实际压力,螺杆位置,速度的方差
    def set_portrait_var(self, ch1, ch2, speed):
        self.pressure_var = ch1
        self.location_var = ch2
        self.speed_var = speed

    # 正样本画像的设定压力
    def set_portrait_pressure_setting(self, pressure_setting):
        self.setting_pressure = pressure_setting

    def set_portrait_total_feat(self, portrait_feat, feat_var):
        self.feature = portrait_feat
        self.feature_var = feat_var

    # 正样本画像的分段点
    def set_seg_points(self, points):
        self.seg_points = points

    def set_every_segs_info(self, each_seg):
        self.each_seg = each_seg

    # 正样本总体的马氏距离平均值与方差
    def set_dist_total_info(self, mean, var):
        self.dist_total_mean = mean
        self.dist_total_var = var

    # 正样本在每段的马氏距离的平均值
    def set_dist_means(self, dist_means):
        self.dist_means = dist_means

    # 正样本在每段的方差
    # 防止方差为0 zxy 修改于 20200114
    def set_dist_vars(self, dist_vars):
        for i in range(0, len(dist_vars)):
            if dist_vars[i] == 0 or dist_vars[0] == 0.0:
                dist_vars[i] += 1e-10
        self.dist_vars = dist_vars

    # 正样本在每段的时间差距的均值
    def set_td_means(self, td_means):
        self.td_means = td_means

    # 正样本在每段的时间差距的方差
    # 防止方差为0 zxy 修改于 20200114
    def set_td_vars(self, td_vars):
        for i in range(0, len(td_vars)):
            if td_vars[i] == 0 or td_vars[0] == 0.0:
                td_vars[i] += 1e-10
        self.td_vars = td_vars

    def check_data_dump_safe(self):
        if self.actual_pressure is not None and self.actual_location is not None and self.actual_speed is not None and self.setting_pressure is not None:
            # print('第一层都正常')
            if self.pressure_var is not None and self.location_var is not None and self.speed_var is not None and self.feature is not None and self.feature_var is not None:
                # print('第二层都正常')
                if self.seg_points is not None and self.each_seg is not None and self.dist_total_mean is not None and self.dist_total_var is not None:
                    # print('第三层都正常')
                    if self.dist_means is not None and self.dist_vars is not None and self.td_means is not None and self.td_vars is not None:
                        # print('第四层都正常')
                        if self.sample_count is not None and self.length is not None:
                            # print('第五层都正常')
                            self.is_result_safe = True
                            # print('self.is_result_safe的值为：',self.is_result_safe)
                            return True
        self.is_result_safe = False
        return False

    def dump_to_db(self):
        if self.check_data_dump_safe():
            dic = {
                'actual_pressure': self.actual_pressure,
                'actual_location': self.actual_location,
                'actual_speed': self.actual_speed,
                'pressure_var': self.pressure_var,
                'location_var': self.location_var,
                'speed_var': self.speed_var,
                'feature': self.feature,
                'feature_var': self.feature_var,
                'setting_pressure': self.setting_pressure,
                'seg_points': self.seg_points,
                'each_seg': self.each_seg,
                # 正样本画像在保压和注射两段的整体马氏距离平均值与方差，以及分段后各段马氏距离平均值与方差,各段时长差距的平均值与方差
                # 用于计算整体置信度，以及分段的异常置信度(通过高斯分布来计算)
                'dist_total_mean': self.dist_total_mean,
                'dist_total_var': self.dist_total_var,
                'dist_means': self.dist_means,
                'dist_vars': self.dist_vars,
                'td_means': self.td_means,
                'td_vars': self.td_vars,
                'sample_count': self.sample_count,
                'length': self.length,
            }
            json_str = json.dumps(dic, cls=NpEncoder)
            return json_str
        else:
            raise Exception("dump_to_db:figure not safe")

    def get_portrait_seg(self,seg_list):
        global_shejiaobaoyafendaun = min(seg_list, key=lambda x: x[2])
        return global_shejiaobaoyafendaun